Setup

library(data.table)
library(dplyr)
Registered S3 methods overwritten by 'tibble':
  method     from  
  format.tbl pillar
  print.tbl  pillar

Attaching package: 'dplyr'
The following objects are masked from 'package:data.table':

    between, first, last
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
library(purrr)

Attaching package: 'purrr'
The following object is masked from 'package:data.table':

    transpose
library(ggplot2)
library(cowplot)

Attaching package: 'cowplot'
The following object is masked from 'package:ggplot2':

    ggsave
source("00_helper_funs.R")

Load data:

d_f <- fread(file.path("..", "data", "cogpsych_data_formatted.csv"))

Get the last observation per learning sequence:

d_last <- d_f[, .SD[.N], by = id]
setorder(d_last, time_between)

Load fitted models:

lr_tau <- fread(file.path("..", "data", "logistic_regression_tau.csv"))
lr_activation <- fread(file.path("..", "data", "logistic_regression_activation.csv"))
bs_d_indiv <- fread(file.path("..", "data", "binary_search_indiv_d.csv"))
bs_h_indiv <- fread(file.path("..", "data", "binary_search_indiv_h.csv"))
tau_short <- fread(file.path("..", "data", "logistic_regression_tau_short.csv"))

Set default parameters for the memory model:

model_params <- list(
  s = .5,
  decay = .5,
  h = 1
)

Set parameters for splitting the data into windows:

n_windows <- c(1:10, 20, 25, 50, 100)

General function for predicting recall for a given parameter configuration:

predict_recall <- function (seq_list, h = model_params$h, decay = model_params$decay, tau, s = model_params$s, windows = 1) {
  
  pred_correct <- map_dbl(seq_list, function (x) {
    
    seq_d <- ifelse(is.data.table(decay), decay[id == x$id, d], decay)
    seq_h <- ifelse(is.data.table(h), h[id == x$id, h], h)
    tau_window <- ifelse(is.data.table(tau), tau[n_windows == windows & window == x$window, tau], tau)
    
    ac <- activation(x$time_within, x$time_between, seq_h, seq_d)
    p_recall(ac, tau_window, s)
    
  })
  
  pred <- data.table(id = map_chr(seq_list, ~.$id), pred_correct = pred_correct)
  
  return (pred)
}

General plot elements

Define the time windows for all splits of the data:

window_range <- map_dfr(n_windows, function (n_w) {

  d_windows <- copy(d_last)
  
  # Split the data into quantiles
  d_windows[, window := dplyr::ntile(time_between, n_w)]

  # Get the window range(s)
  window_range <- d_windows[, .(start = min(time_between), end = max(time_between)), by = .(window)]
  window_range[, geom_mean := sqrt(start*end), by = .(window)]
  setorder(window_range, window)
  window_range[, window := window]
  window_range[, n_windows := n_w]

  return (window_range)
})

Plot colours:

pred_col <- "#CC3311"    # red
obs_col <- "#000000"     # black
window_col <- "#33BBEE"  # blue
section_col <- "#FD520F" # orange

Interpretable time labels:

label_x <- c(1, 10, 60, 6*60, 24*60, 7*24*60, 7*7*24*60)
label_y <- 1.09
label_txt <- c("1 min", "10 min", "1 h", "6 h", "24 h", "1 wk", "7 wks")

General function for plotting comparison between data and model:

plot_comparison <- function (d_model,
                             n_w = 1,
                             label_pos = list(data = list(x = 35000, y = .54), 
                                              model = list(x = 35000, y = .46)),
                             print_plot = TRUE) {
  
  
  plot_dodge <- function(y, dodge = .1) {
    return (y * (1 + dodge) - dodge/2)
  }
  
  p <- ggplot() +
    # Window background
    geom_rect(data = window_range[n_windows == n_w], aes(xmin = start/60, xmax = end/60, ymin = -Inf, ymax = Inf, alpha = as.factor(window)), fill = window_col) +
    # Jittered observations along edges
    geom_point(data = d_last, 
               aes(x = time_between/60, y = plot_dodge(correct, .05)),
               position = position_jitter(width = 0, height = .025, seed = 123),
               colour = "grey20", size = .001, pch = ".", alpha = .1) +
    # Time markers along the top
    geom_segment(aes(x = label_x, xend = label_x, y = 1.05, yend = 1.065),
                 colour = "grey20") +
    geom_text(aes(x = label_x, y = label_y, label = label_txt),
              colour = "grey30") +
    # GAM: data
    geom_smooth(data = d_last,
                aes(x = time_between/60, y = correct),
                method = "gam", formula = y ~ s(x, bs = "cs"),
                colour = obs_col, lty = 1, lwd = 1) +
    # GAM: model
    geom_smooth(data = d_model, 
                aes(x = time_between/60, y = pred_correct),
                method = "gam", formula = y ~ s(x, bs = "cs"), 
                colour = pred_col, fill = pred_col, lty = 1, lwd = .75) +
    # Labels
    annotate("text", x = label_pos$data$x, y = label_pos$data$y,
             label = "Data", colour = obs_col) +
    annotate("text", x = label_pos$model$x, y = label_pos$model$y,
             label = "Model", colour = pred_col) +
    # Plot setup
    scale_x_log10(
      breaks = scales::trans_breaks("log10", function(x) 10^x),
      labels = scales::trans_format("log10", scales::math_format(10^.x)),
      expand = c(0, 0)
    ) +
    scale_y_continuous(breaks = seq(0, 1, by = .25), labels = scales::percent_format()) +
    scale_alpha_manual(values = rep(c(.1, .25), ceiling(n_w/2))) +
    guides(colour = "none",
           alpha = "none") +
    labs(x = "Between-session interval (minutes)",
         y = "Response accuracy") +
    annotation_logticks(sides = "b", outside = T) +
    coord_cartesian(ylim = c(0, 1), xlim = c(window_range[1, start], window_range[.N, end])/60, clip = "off") +
    theme_bw(base_size = 14) +
    theme(plot.margin = margin(21, 14, 7, 7),
          panel.grid.major.x = element_blank(),
          panel.grid.minor = element_blank(),
          panel.border = element_blank())
  
  if (print_plot) print(p)
  return (p)
    
}

General function for plotting parameter change over time:

plot_parameter <- function(d_parameter,
                           n_w = 1,
                           log_x = TRUE,
                           log_y = FALSE,
                           print_plot = TRUE) {
  
  # Calculate R-squared
  x <- d_parameter[n_windows == n_w, geom_mean]
  y <- d_parameter[n_windows == n_w, parameter]
  if (log_x) x <- log(x)
  if (log_y) y <- log(y)
  
  m <- lm(y ~ x)
  rsq <- paste("R^2 ==", scales::number(summary(m)$r.squared, accuracy = .01))

  p <- ggplot() +
    # Window background
    geom_rect(data = window_range[n_windows == n_w], 
              aes(xmin = start/60, xmax = end/60, ymin = ifelse(log_y, 0, -Inf), ymax = Inf, alpha = as.factor(window)), 
              fill = window_col) +
    # Regression line
    geom_smooth(data = d_parameter[n_windows == n_w], 
                aes(y = parameter, x = geom_mean/60), 
                method = "lm", formula = y ~ x, 
                colour = pred_col, fill = pred_col) +
    # Parameter values
    geom_point(data = d_parameter[n_windows == n_w],
               aes(y = parameter, x = geom_mean/60)) +
    scale_alpha_manual(values = rep(c(.25, .1), ceiling(n_w/2))) +
    # R-squared
    geom_text(aes(x = Inf, y = Inf, label = rsq),
              hjust = "inward", vjust = "inward",
              parse = TRUE) +
    # Plot setup
    guides(alpha = "none") +
    labs(x = "Between-session interval (minutes)",
         y = "Fitted parameter") +
    coord_cartesian(xlim = c(window_range[1, start], window_range[.N, end])/60, clip = "off") +
    theme_bw(base_size = 14) +
    theme(plot.margin = margin(21, 14, 7, 7),
          panel.grid.major.x = element_blank(),
          panel.grid.minor = element_blank(),
          panel.border = element_blank())
  
  
  # Transform scales if required
  if (log_x) {
    p <- p +
      scale_x_log10(
        breaks = scales::trans_breaks("log10", function(x) 10^x),
        labels = scales::trans_format("log10", scales::math_format(10^.x)),
        expand = c(0, 0)
      ) +
      annotation_logticks(sides = "b", outside = T)
  }
  
  if (log_y) {
    p <- p +
      scale_y_log10() +
      annotation_logticks(sides = "l", outside = T)
  }

  if (print_plot) print(p)
  return (p)

}

Single parameter

What does predicted retention look like if we use a single parameter configuration fitted across the whole range of intervals?

Divide the data into learning sequences for plotting:

d_single <- copy(d_last)
d_single[, sequence := 1:.N]
d_single <- d_f[d_single[, .(id, sequence)], on = .(id)]
d_single[, window := 1]
d_seq_list_single <- generate_seq_list(d_single)

Whole data set

Threshold

single_tau <- lr_tau[n_windows == 1, tau]

The best-fitting threshold for the entire range of intervals is -4.6072046. If we consistently use this threshold in the model, it initially overpredicts retention (indicating that the threshold is too low relative to the activation), gets the prediction right around the mode of the interval distribution, and eventually underpredicts retention (indicating that the threshold is too high).

pred_single_tau <- predict_recall(seq_list = d_seq_list_single, tau = single_tau)
d_single_tau <- d_last[pred_single_tau, on = .(id)]

p_single_tau <- plot_comparison(d_model = d_single_tau, 
                                n_w = 1, 
                                label_pos = list(data = list(x = 35000, y = .5), 
                                                 model = list(x = 7000, y = .29)))

Decay

We did not determine the decay directly, but derived it from the best-fitting activation. Across the whole data set, the best-fitting activation is -4.1130682. We determined the optimal decay for each item, such that it ended up at this activation. Since activation (and the threshold) are constant across the range, the predicted recall is also simply the best-fitting horizontal line through the data.

pred_single_d <- predict_recall(d_seq_list_single,
                                decay = bs_d_indiv[n_windows == 1], 
                                tau = lr_activation[n_windows == 1, tau])
d_single_d <- d_last[pred_single_d, on = .(id)]

p_single_d <- plot_comparison(d_model = d_single_d, 
                              n_w = 1, 
                              label_pos = list(data = list(x = 35000, y = .5), 
                                               model = list(x = 35000, y = .78)))
Warning in newton(lsp = lsp, X = G$X, y = G$y, Eb = G$Eb, UrS = G$UrS, L =
G$L, : Fitting terminated with step failure - check results carefully

Scaling factor

Like the decay, the optimal scaling factor was derived from the best-fitting activation. In principle this should again produce a horizontal line. However, getting this activation value would require scaling by a factor larger than 1 for a large portion of the data. Since h is limited to 1, we cannot get the activation low enough in these cases, and so we overpredict retention.

pred_single_h <- predict_recall(d_seq_list_single,
                                h = bs_h_indiv[n_windows == 1],
                                tau = lr_activation[n_windows == 1, tau])
d_single_h <- d_last[pred_single_h, on = .(id)]

p_single_h <- plot_comparison(d_model = d_single_h, 
                              n_w = 1, 
                              label_pos = list(data = list(x = 35000, y = .5), 
                                               model = list(x = 35000, y = .78)))

24-hour interval

Rather than determining the best parameter fit from the whole data set, we also explore a scenario in which we only have intervals around 24 hours, which is more similar to many controlled experiments.

We use a subset of the data with intervals around 24 hours. In the 20-window fit this falls quite neatly within window number 12, so we’ll use that.

ggplot(window_range[n_windows == 20]) +
  geom_vline(aes(xintercept = 24*60*60), colour = "red", lty = 2) +
  geom_hline(aes(yintercept = 12), colour = "red", lty = 2) +
  geom_segment(aes(x = start, xend = end, y = window, yend = window)) +
  geom_point(aes(x = geom_mean, y = window)) +
  geom_text(aes(x = geom_mean, y = window + .5, label = window), colour = "blue") +
  scale_x_log10(
    breaks = scales::trans_breaks("log10", function(x) 10^x),
    labels = scales::trans_format("log10", scales::math_format(10^.x)),
    expand = c(0, 0)
  ) +
  labs(x = "Between-session interval (minutes)",
       y = "Window") +
  annotation_logticks(sides = "b", outside = T) +
  coord_cartesian(clip = "off")

Threshold

tau_24h <- lr_tau[n_windows == 20 & window == 12, tau]

The optimal threshold for the 24h interval is -4.6927408, quite similar to the optimal threshold for the whole data set. The model’s predictions also look very similar.

pred_tau_24h <- predict_recall(seq_list = d_seq_list_single, tau = tau_24h)
d_tau_24h <- d_last[pred_tau_24h, on = .(id)]

p_tau_24h <- plot_comparison(d_model = d_tau_24h, 
                             n_w = 1, 
                             label_pos = list(data = list(x = 35000, y = .5), 
                                              model = list(x = 5000, y = .29))) +
  geom_rect(data = window_range[n_windows == 20 & window == 12], aes(xmin  = start/60, xmax = end/60, ymin = -0.05, ymax = 1.05), fill = section_col, alpha = .25)

p_tau_24h

Decay

Because optimal decay is derived separately for each individual learning sequence, there is not a single decay value associated with the 24h interval, and we only know the best decay for the individual sequences that fall within the 24h set.

d_24h <- median(bs_d_indiv[n_windows == 20 & window == 12, d])

To get a parameter that we can use for general prediction, we’ll take the median: d = 0.3147813.

ggplot(bs_d_indiv[n_windows == 20 & window == 12], aes(x = d)) +
  geom_histogram(binwidth = .005, fill = "midnightblue") +
  geom_vline(aes(xintercept = d_24h), colour = "red", lty = 2)

These predictions show the typical pattern: the model initially overpredicts recall, gets it right around 24h, and eventually underpredicts recall.

pred_d_24h <- predict_recall(d_seq_list_single,
                             decay = d_24h, 
                             tau = lr_activation[n_windows == 20 & window == 12, tau])
d_d_24h <- d_last[pred_d_24h, on = .(id)]

p_d_24h <- plot_comparison(d_model = d_d_24h, 
                           n_w = 1, 
                           label_pos = list(data = list(x = 35000, y = .5), 
                                            model = list(x = 5000, y = .78))) +
  geom_rect(data = window_range[n_windows == 20 & window == 12], aes(xmin  = start/60, xmax = end/60, ymin = -0.05, ymax = 1.05), fill = section_col, alpha = .25)

p_d_24h

Scaling factor

h_24h <- median(bs_h_indiv[n_windows == 20 & window == 12, h])

As with decay, we’ll take the median scaling factor from the 24h set: h = 0.0116223.

ggplot(bs_h_indiv[n_windows == 20 & window == 12], aes(x = h)) +
  geom_histogram(binwidth = .01, fill = "midnightblue") +
  geom_vline(aes(xintercept = h_24h), colour = "red", lty = 2)

This model already does surprisingly well: short-term predictions are good, though the model is too optimistic on intervals of 1-24 hours, and too pessimistic on intervals longer than a week.

pred_h_24h <- predict_recall(d_seq_list_single,
                             h = h_24h,
                             tau = lr_activation[n_windows == 20 & window == 12, tau])
d_h_24h <- d_last[pred_h_24h, on = .(id)]

p_h_24h <- plot_comparison(d_model = d_h_24h, 
                           n_w = 1, 
                           label_pos = list(data = list(x = 35000, y = .5), 
                                            model = list(x = 12000, y = .2))) +
  geom_rect(data = window_range[n_windows == 20 & window == 12], aes(xmin  = start/60, xmax = end/60, ymin = -0.05, ymax = 1.05), fill = section_col, alpha = .25)

p_h_24h

Short intervals

Threshold

Finally, we’ll look at a model with default parameters other than the threshold, which is fitted to short intervals (0-10 minutes) only.

pred_tau_short <- predict_recall(d_seq_list_single,
                                 tau = tau_short$tau_short)
d_tau_short <- d_last[pred_tau_short, on = .(id)]

p_tau_short <- plot_comparison(d_model = d_tau_short, 
                               n_w = 1, 
                               label_pos = list(data = list(x = 35000, y = .5), 
                                                model = list(x = 35000, y = .08))) +
  geom_rect(aes(xmin  = min(window_range$start)/60, xmax = 10, ymin = -0.05, ymax = 1.05), fill = section_col, alpha = .25)

p_tau_short

Time-dependent parameters

Rather than using a single parameter configuration across the range, we can also fit parameters separately per time bin. Here we’ll look at the results when the data are split into 20 bins, each containing 5% of the total.

Prepare the data:

d_20 <- copy(d_last)
d_20 <- d_20[, sequence := 1:.N][, .(id, sequence, time_between, window = ntile(time_between, 20))]
d_20_seq <- generate_seq_list(d_20[d_f, on = .(id)])

Threshold

pred_tau_20 <- predict_recall(seq_list = d_20_seq, tau = lr_tau, windows = 20)
d_tau_20 <- d_last[pred_tau_20, on = .(id)]

p_tau_20 <- plot_comparison(d_model = d_tau_20, 
                            n_w = 20, 
                            label_pos = list(data = list(x = 35000, y = .5), 
                                             model = list(x = 20000, y = .29)))

Parameter change over time:

lr_tau_viz <- lr_tau[window_range, on = .(n_windows, window)]
setnames(lr_tau_viz, "tau", "parameter")

p_tau_time <- plot_parameter(d_parameter = lr_tau_viz,
                             n_w = 20)

Decay

pred_d_20 <- predict_recall(seq_list = d_20_seq,
                            decay = bs_d_indiv[n_windows == 20], 
                            tau = lr_activation,
                            windows = 20)

d_d_20 <- d_last[pred_d_20, on = .(id)]

p_d_20 <- plot_comparison(d_model = d_d_20, 
                          n_w = 20, 
                          label_pos = list(data = list(x = 30000, y = .3), 
                                           model = list(x = 40000, y = .54)))

Parameter change over time:

bs_d_viz <- bs_d_indiv[window_range, on = .(n_windows, window)][, .(d = median(d)), by = .(n_windows, window, start, end, geom_mean)]
setnames(bs_d_viz, "d", "parameter")

p_d_time <- plot_parameter(d_parameter = bs_d_viz,
                           n_w = 20)

Scaling factor

pred_h_20 <- predict_recall(seq_list = d_20_seq,
                            h = bs_h_indiv[n_windows == 20], 
                            tau = lr_activation,
                            windows = 20)

d_h_20 <- d_last[pred_h_20, on = .(id)]

p_h_20 <- plot_comparison(d_model = d_h_20, 
                          n_w = 20, 
                          label_pos = list(data = list(x = 30000, y = .3), 
                                           model = list(x = 40000, y = .54)))

Parameter change over time:

bs_h_viz <- bs_h_indiv[window_range, on = .(n_windows, window)][, .(h = median(h)), by = .(n_windows, window, start, end, geom_mean)]
setnames(bs_h_viz, "h", "parameter")

p_h_time <- plot_parameter(d_parameter = bs_h_viz,
                           log_y = TRUE,
                           n_w = 20)
Warning: Transformation introduced infinite values in continuous y-axis

Interval distribution

A histogram of the between-session intervals in the data.

p_histogram <- ggplot() +
  # Window background
  geom_rect(data = window_range[n_windows == 1], aes(xmin = start/60, xmax = end/60, ymin = -Inf, ymax = Inf), fill = window_col, alpha = .1) +
  # Time markers along the top
  geom_segment(aes(x = label_x, xend = label_x, y = 1.05, yend = 1.065), colour = "grey20") +
  geom_text(aes(x = label_x, y = label_y, label = label_txt), colour = "grey30") +
  # Histogram
  geom_histogram(data = d_last, aes(x = time_between/60, y = ..ncount..), bins = 100, fill = obs_col) +
  # Plot setup
  scale_x_log10(
    breaks = scales::trans_breaks("log10", function(x) 10^x),
    labels = scales::trans_format("log10", scales::math_format(10^.x)),
    expand = c(0, 0)
  ) +
  scale_y_continuous(breaks = seq(0, 1, by = .25)) +
  labs(x = "Between-session interval (minutes)",
       y = "Density") +
  annotation_logticks(sides = "b", outside = T) +
  coord_cartesian(ylim = c(0, 1), xlim = c(window_range[1, start], window_range[.N, end])/60, clip = "off") +
  theme_bw(base_size = 14) +
  theme(plot.margin = margin(21, 14, 7, 7),
        panel.grid.major.x = element_blank(),
        panel.grid.minor = element_blank(),
        panel.border = element_blank())

p_histogram

Combined figures

Model fits

plot_grid(
  p_histogram,
  p_tau_short,
  p_tau_24h,
  p_tau_20,
  p_d_24h,
  p_d_20,
  p_h_24h,
  p_h_20,
  ncol = 2,
  labels = c("A\t\tInterval distribution", "B\t\tThreshold optimised for 0 - 10 min", "C\t\tThreshold optimised for 24h", "D\t\tInterval-dependent threshold", "E\t\tDecay optimised for 24h", "F\t\tInterval-dependent decay", "G\t\tScaling factor optimised for 24h", "H\t\tInterval-dependent scaling factor"),
  align = "hv",
  label_x = .025,
  hjust = 0,
  scale = .9
) +
  theme(plot.background = element_rect(fill = "white", colour = NA))

ggsave(file.path("..", "output", "model_fitting_results.png"), width = 10, height = 15)

Parameters over time

plot_grid(
  p_tau_time, p_d_time, p_h_time,
  ncol = 3, 
  labels = c("A\t\tInterval-dependent threshold", "B\t\tInterval-dependent decay", "C\t\tInterval-dependent h"),
  align = "hv",
  label_x = .025,
  hjust = 0
) +
  theme(plot.background = element_rect(fill = "white", colour = NA))
Warning: Transformation introduced infinite values in continuous y-axis

ggsave(file.path("..", "output", "params_time.png"), width = 12, height = 4)
LS0tCnRpdGxlOiAiUGxvdCBtb2RlbGxpbmcgcmVzdWx0cyIKYXV0aG9yOiAiTWFhcnRlbiB2YW4gZGVyIFZlbGRlIgpkYXRlOiAiTGFzdCB1cGRhdGVkOiBgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBzbWFydDogbm8KICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogIGdpdGh1Yl9kb2N1bWVudDoKICAgIHRvYzogeWVzCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKIyBTZXR1cAoKYGBge3J9CmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShkcGx5cikKbGlicmFyeShwdXJycikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNvd3Bsb3QpCgpzb3VyY2UoIjAwX2hlbHBlcl9mdW5zLlIiKQpgYGAKCgpMb2FkIGRhdGE6CmBgYHtyfQpkX2YgPC0gZnJlYWQoZmlsZS5wYXRoKCIuLiIsICJkYXRhIiwgImNvZ3BzeWNoX2RhdGFfZm9ybWF0dGVkLmNzdiIpKQpgYGAKCkdldCB0aGUgbGFzdCBvYnNlcnZhdGlvbiBwZXIgbGVhcm5pbmcgc2VxdWVuY2U6CmBgYHtyfQpkX2xhc3QgPC0gZF9mWywgLlNEWy5OXSwgYnkgPSBpZF0Kc2V0b3JkZXIoZF9sYXN0LCB0aW1lX2JldHdlZW4pCmBgYAoKTG9hZCBmaXR0ZWQgbW9kZWxzOgpgYGB7cn0KbHJfdGF1IDwtIGZyZWFkKGZpbGUucGF0aCgiLi4iLCAiZGF0YSIsICJsb2dpc3RpY19yZWdyZXNzaW9uX3RhdS5jc3YiKSkKbHJfYWN0aXZhdGlvbiA8LSBmcmVhZChmaWxlLnBhdGgoIi4uIiwgImRhdGEiLCAibG9naXN0aWNfcmVncmVzc2lvbl9hY3RpdmF0aW9uLmNzdiIpKQpic19kX2luZGl2IDwtIGZyZWFkKGZpbGUucGF0aCgiLi4iLCAiZGF0YSIsICJiaW5hcnlfc2VhcmNoX2luZGl2X2QuY3N2IikpCmJzX2hfaW5kaXYgPC0gZnJlYWQoZmlsZS5wYXRoKCIuLiIsICJkYXRhIiwgImJpbmFyeV9zZWFyY2hfaW5kaXZfaC5jc3YiKSkKdGF1X3Nob3J0IDwtIGZyZWFkKGZpbGUucGF0aCgiLi4iLCAiZGF0YSIsICJsb2dpc3RpY19yZWdyZXNzaW9uX3RhdV9zaG9ydC5jc3YiKSkKYGBgCgpTZXQgZGVmYXVsdCBwYXJhbWV0ZXJzIGZvciB0aGUgbWVtb3J5IG1vZGVsOgpgYGB7cn0KbW9kZWxfcGFyYW1zIDwtIGxpc3QoCiAgcyA9IC41LAogIGRlY2F5ID0gLjUsCiAgaCA9IDEKKQpgYGAKClNldCBwYXJhbWV0ZXJzIGZvciBzcGxpdHRpbmcgdGhlIGRhdGEgaW50byB3aW5kb3dzOgpgYGB7cn0Kbl93aW5kb3dzIDwtIGMoMToxMCwgMjAsIDI1LCA1MCwgMTAwKQpgYGAKCkdlbmVyYWwgZnVuY3Rpb24gZm9yIHByZWRpY3RpbmcgcmVjYWxsIGZvciBhIGdpdmVuIHBhcmFtZXRlciBjb25maWd1cmF0aW9uOgpgYGB7cn0KcHJlZGljdF9yZWNhbGwgPC0gZnVuY3Rpb24gKHNlcV9saXN0LCBoID0gbW9kZWxfcGFyYW1zJGgsIGRlY2F5ID0gbW9kZWxfcGFyYW1zJGRlY2F5LCB0YXUsIHMgPSBtb2RlbF9wYXJhbXMkcywgd2luZG93cyA9IDEpIHsKICAKICBwcmVkX2NvcnJlY3QgPC0gbWFwX2RibChzZXFfbGlzdCwgZnVuY3Rpb24gKHgpIHsKICAgIAogICAgc2VxX2QgPC0gaWZlbHNlKGlzLmRhdGEudGFibGUoZGVjYXkpLCBkZWNheVtpZCA9PSB4JGlkLCBkXSwgZGVjYXkpCiAgICBzZXFfaCA8LSBpZmVsc2UoaXMuZGF0YS50YWJsZShoKSwgaFtpZCA9PSB4JGlkLCBoXSwgaCkKICAgIHRhdV93aW5kb3cgPC0gaWZlbHNlKGlzLmRhdGEudGFibGUodGF1KSwgdGF1W25fd2luZG93cyA9PSB3aW5kb3dzICYgd2luZG93ID09IHgkd2luZG93LCB0YXVdLCB0YXUpCiAgICAKICAgIGFjIDwtIGFjdGl2YXRpb24oeCR0aW1lX3dpdGhpbiwgeCR0aW1lX2JldHdlZW4sIHNlcV9oLCBzZXFfZCkKICAgIHBfcmVjYWxsKGFjLCB0YXVfd2luZG93LCBzKQogICAgCiAgfSkKICAKICBwcmVkIDwtIGRhdGEudGFibGUoaWQgPSBtYXBfY2hyKHNlcV9saXN0LCB+LiRpZCksIHByZWRfY29ycmVjdCA9IHByZWRfY29ycmVjdCkKICAKICByZXR1cm4gKHByZWQpCn0KYGBgCgojIyBHZW5lcmFsIHBsb3QgZWxlbWVudHMKCkRlZmluZSB0aGUgdGltZSB3aW5kb3dzIGZvciBhbGwgc3BsaXRzIG9mIHRoZSBkYXRhOgpgYGB7cn0Kd2luZG93X3JhbmdlIDwtIG1hcF9kZnIobl93aW5kb3dzLCBmdW5jdGlvbiAobl93KSB7CgogIGRfd2luZG93cyA8LSBjb3B5KGRfbGFzdCkKICAKICAjIFNwbGl0IHRoZSBkYXRhIGludG8gcXVhbnRpbGVzCiAgZF93aW5kb3dzWywgd2luZG93IDo9IGRwbHlyOjpudGlsZSh0aW1lX2JldHdlZW4sIG5fdyldCgogICMgR2V0IHRoZSB3aW5kb3cgcmFuZ2UocykKICB3aW5kb3dfcmFuZ2UgPC0gZF93aW5kb3dzWywgLihzdGFydCA9IG1pbih0aW1lX2JldHdlZW4pLCBlbmQgPSBtYXgodGltZV9iZXR3ZWVuKSksIGJ5ID0gLih3aW5kb3cpXQogIHdpbmRvd19yYW5nZVssIGdlb21fbWVhbiA6PSBzcXJ0KHN0YXJ0KmVuZCksIGJ5ID0gLih3aW5kb3cpXQogIHNldG9yZGVyKHdpbmRvd19yYW5nZSwgd2luZG93KQogIHdpbmRvd19yYW5nZVssIHdpbmRvdyA6PSB3aW5kb3ddCiAgd2luZG93X3JhbmdlWywgbl93aW5kb3dzIDo9IG5fd10KCiAgcmV0dXJuICh3aW5kb3dfcmFuZ2UpCn0pCmBgYAoKUGxvdCBjb2xvdXJzOgpgYGB7cn0KcHJlZF9jb2wgPC0gIiNDQzMzMTEiICAgICMgcmVkCm9ic19jb2wgPC0gIiMwMDAwMDAiICAgICAjIGJsYWNrCndpbmRvd19jb2wgPC0gIiMzM0JCRUUiICAjIGJsdWUKc2VjdGlvbl9jb2wgPC0gIiNGRDUyMEYiICMgb3JhbmdlCmBgYAoKSW50ZXJwcmV0YWJsZSB0aW1lIGxhYmVsczoKYGBge3J9CmxhYmVsX3ggPC0gYygxLCAxMCwgNjAsIDYqNjAsIDI0KjYwLCA3KjI0KjYwLCA3KjcqMjQqNjApCmxhYmVsX3kgPC0gMS4wOQpsYWJlbF90eHQgPC0gYygiMSBtaW4iLCAiMTAgbWluIiwgIjEgaCIsICI2IGgiLCAiMjQgaCIsICIxIHdrIiwgIjcgd2tzIikKYGBgCgoKCkdlbmVyYWwgZnVuY3Rpb24gZm9yIHBsb3R0aW5nIGNvbXBhcmlzb24gYmV0d2VlbiBkYXRhIGFuZCBtb2RlbDoKYGBge3J9CnBsb3RfY29tcGFyaXNvbiA8LSBmdW5jdGlvbiAoZF9tb2RlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3cgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsX3BvcyA9IGxpc3QoZGF0YSA9IGxpc3QoeCA9IDM1MDAwLCB5ID0gLjU0KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IGxpc3QoeCA9IDM1MDAwLCB5ID0gLjQ2KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfcGxvdCA9IFRSVUUpIHsKICAKICAKICBwbG90X2RvZGdlIDwtIGZ1bmN0aW9uKHksIGRvZGdlID0gLjEpIHsKICAgIHJldHVybiAoeSAqICgxICsgZG9kZ2UpIC0gZG9kZ2UvMikKICB9CiAgCiAgcCA8LSBnZ3Bsb3QoKSArCiAgICAjIFdpbmRvdyBiYWNrZ3JvdW5kCiAgICBnZW9tX3JlY3QoZGF0YSA9IHdpbmRvd19yYW5nZVtuX3dpbmRvd3MgPT0gbl93XSwgYWVzKHhtaW4gPSBzdGFydC82MCwgeG1heCA9IGVuZC82MCwgeW1pbiA9IC1JbmYsIHltYXggPSBJbmYsIGFscGhhID0gYXMuZmFjdG9yKHdpbmRvdykpLCBmaWxsID0gd2luZG93X2NvbCkgKwogICAgIyBKaXR0ZXJlZCBvYnNlcnZhdGlvbnMgYWxvbmcgZWRnZXMKICAgIGdlb21fcG9pbnQoZGF0YSA9IGRfbGFzdCwgCiAgICAgICAgICAgICAgIGFlcyh4ID0gdGltZV9iZXR3ZWVuLzYwLCB5ID0gcGxvdF9kb2RnZShjb3JyZWN0LCAuMDUpKSwKICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLCBoZWlnaHQgPSAuMDI1LCBzZWVkID0gMTIzKSwKICAgICAgICAgICAgICAgY29sb3VyID0gImdyZXkyMCIsIHNpemUgPSAuMDAxLCBwY2ggPSAiLiIsIGFscGhhID0gLjEpICsKICAgICMgVGltZSBtYXJrZXJzIGFsb25nIHRoZSB0b3AKICAgIGdlb21fc2VnbWVudChhZXMoeCA9IGxhYmVsX3gsIHhlbmQgPSBsYWJlbF94LCB5ID0gMS4wNSwgeWVuZCA9IDEuMDY1KSwKICAgICAgICAgICAgICAgICBjb2xvdXIgPSAiZ3JleTIwIikgKwogICAgZ2VvbV90ZXh0KGFlcyh4ID0gbGFiZWxfeCwgeSA9IGxhYmVsX3ksIGxhYmVsID0gbGFiZWxfdHh0KSwKICAgICAgICAgICAgICBjb2xvdXIgPSAiZ3JleTMwIikgKwogICAgIyBHQU06IGRhdGEKICAgIGdlb21fc21vb3RoKGRhdGEgPSBkX2xhc3QsCiAgICAgICAgICAgICAgICBhZXMoeCA9IHRpbWVfYmV0d2Vlbi82MCwgeSA9IGNvcnJlY3QpLAogICAgICAgICAgICAgICAgbWV0aG9kID0gImdhbSIsIGZvcm11bGEgPSB5IH4gcyh4LCBicyA9ICJjcyIpLAogICAgICAgICAgICAgICAgY29sb3VyID0gb2JzX2NvbCwgbHR5ID0gMSwgbHdkID0gMSkgKwogICAgIyBHQU06IG1vZGVsCiAgICBnZW9tX3Ntb290aChkYXRhID0gZF9tb2RlbCwgCiAgICAgICAgICAgICAgICBhZXMoeCA9IHRpbWVfYmV0d2Vlbi82MCwgeSA9IHByZWRfY29ycmVjdCksCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2FtIiwgZm9ybXVsYSA9IHkgfiBzKHgsIGJzID0gImNzIiksIAogICAgICAgICAgICAgICAgY29sb3VyID0gcHJlZF9jb2wsIGZpbGwgPSBwcmVkX2NvbCwgbHR5ID0gMSwgbHdkID0gLjc1KSArCiAgICAjIExhYmVscwogICAgYW5ub3RhdGUoInRleHQiLCB4ID0gbGFiZWxfcG9zJGRhdGEkeCwgeSA9IGxhYmVsX3BvcyRkYXRhJHksCiAgICAgICAgICAgICBsYWJlbCA9ICJEYXRhIiwgY29sb3VyID0gb2JzX2NvbCkgKwogICAgYW5ub3RhdGUoInRleHQiLCB4ID0gbGFiZWxfcG9zJG1vZGVsJHgsIHkgPSBsYWJlbF9wb3MkbW9kZWwkeSwKICAgICAgICAgICAgIGxhYmVsID0gIk1vZGVsIiwgY29sb3VyID0gcHJlZF9jb2wpICsKICAgICMgUGxvdCBzZXR1cAogICAgc2NhbGVfeF9sb2cxMCgKICAgICAgYnJlYWtzID0gc2NhbGVzOjp0cmFuc19icmVha3MoImxvZzEwIiwgZnVuY3Rpb24oeCkgMTBeeCksCiAgICAgIGxhYmVscyA9IHNjYWxlczo6dHJhbnNfZm9ybWF0KCJsb2cxMCIsIHNjYWxlczo6bWF0aF9mb3JtYXQoMTBeLngpKSwKICAgICAgZXhwYW5kID0gYygwLCAwKQogICAgKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEsIGJ5ID0gLjI1KSwgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArCiAgICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzID0gcmVwKGMoLjEsIC4yNSksIGNlaWxpbmcobl93LzIpKSkgKwogICAgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwKICAgICAgICAgICBhbHBoYSA9ICJub25lIikgKwogICAgbGFicyh4ID0gIkJldHdlZW4tc2Vzc2lvbiBpbnRlcnZhbCAobWludXRlcykiLAogICAgICAgICB5ID0gIlJlc3BvbnNlIGFjY3VyYWN5IikgKwogICAgYW5ub3RhdGlvbl9sb2d0aWNrcyhzaWRlcyA9ICJiIiwgb3V0c2lkZSA9IFQpICsKICAgIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCAxKSwgeGxpbSA9IGMod2luZG93X3JhbmdlWzEsIHN0YXJ0XSwgd2luZG93X3JhbmdlWy5OLCBlbmRdKS82MCwgY2xpcCA9ICJvZmYiKSArCiAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkgKwogICAgdGhlbWUocGxvdC5tYXJnaW4gPSBtYXJnaW4oMjEsIDE0LCA3LCA3KSwKICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpCiAgCiAgaWYgKHByaW50X3Bsb3QpIHByaW50KHApCiAgcmV0dXJuIChwKQogICAgCn0KYGBgCgpHZW5lcmFsIGZ1bmN0aW9uIGZvciBwbG90dGluZyBwYXJhbWV0ZXIgY2hhbmdlIG92ZXIgdGltZToKYGBge3J9CnBsb3RfcGFyYW1ldGVyIDwtIGZ1bmN0aW9uKGRfcGFyYW1ldGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX3cgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dfeCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvZ195ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3Bsb3QgPSBUUlVFKSB7CiAgCiAgIyBDYWxjdWxhdGUgUi1zcXVhcmVkCiAgeCA8LSBkX3BhcmFtZXRlcltuX3dpbmRvd3MgPT0gbl93LCBnZW9tX21lYW5dCiAgeSA8LSBkX3BhcmFtZXRlcltuX3dpbmRvd3MgPT0gbl93LCBwYXJhbWV0ZXJdCiAgaWYgKGxvZ194KSB4IDwtIGxvZyh4KQogIGlmIChsb2dfeSkgeSA8LSBsb2coeSkKICAKICBtIDwtIGxtKHkgfiB4KQogIHJzcSA8LSBwYXN0ZSgiUl4yID09Iiwgc2NhbGVzOjpudW1iZXIoc3VtbWFyeShtKSRyLnNxdWFyZWQsIGFjY3VyYWN5ID0gLjAxKSkKCiAgcCA8LSBnZ3Bsb3QoKSArCiAgICAjIFdpbmRvdyBiYWNrZ3JvdW5kCiAgICBnZW9tX3JlY3QoZGF0YSA9IHdpbmRvd19yYW5nZVtuX3dpbmRvd3MgPT0gbl93XSwgCiAgICAgICAgICAgICAgYWVzKHhtaW4gPSBzdGFydC82MCwgeG1heCA9IGVuZC82MCwgeW1pbiA9IGlmZWxzZShsb2dfeSwgMCwgLUluZiksIHltYXggPSBJbmYsIGFscGhhID0gYXMuZmFjdG9yKHdpbmRvdykpLCAKICAgICAgICAgICAgICBmaWxsID0gd2luZG93X2NvbCkgKwogICAgIyBSZWdyZXNzaW9uIGxpbmUKICAgIGdlb21fc21vb3RoKGRhdGEgPSBkX3BhcmFtZXRlcltuX3dpbmRvd3MgPT0gbl93XSwgCiAgICAgICAgICAgICAgICBhZXMoeSA9IHBhcmFtZXRlciwgeCA9IGdlb21fbWVhbi82MCksIAogICAgICAgICAgICAgICAgbWV0aG9kID0gImxtIiwgZm9ybXVsYSA9IHkgfiB4LCAKICAgICAgICAgICAgICAgIGNvbG91ciA9IHByZWRfY29sLCBmaWxsID0gcHJlZF9jb2wpICsKICAgICMgUGFyYW1ldGVyIHZhbHVlcwogICAgZ2VvbV9wb2ludChkYXRhID0gZF9wYXJhbWV0ZXJbbl93aW5kb3dzID09IG5fd10sCiAgICAgICAgICAgICAgIGFlcyh5ID0gcGFyYW1ldGVyLCB4ID0gZ2VvbV9tZWFuLzYwKSkgKwogICAgc2NhbGVfYWxwaGFfbWFudWFsKHZhbHVlcyA9IHJlcChjKC4yNSwgLjEpLCBjZWlsaW5nKG5fdy8yKSkpICsKICAgICMgUi1zcXVhcmVkCiAgICBnZW9tX3RleHQoYWVzKHggPSBJbmYsIHkgPSBJbmYsIGxhYmVsID0gcnNxKSwKICAgICAgICAgICAgICBoanVzdCA9ICJpbndhcmQiLCB2anVzdCA9ICJpbndhcmQiLAogICAgICAgICAgICAgIHBhcnNlID0gVFJVRSkgKwogICAgIyBQbG90IHNldHVwCiAgICBndWlkZXMoYWxwaGEgPSAibm9uZSIpICsKICAgIGxhYnMoeCA9ICJCZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWwgKG1pbnV0ZXMpIiwKICAgICAgICAgeSA9ICJGaXR0ZWQgcGFyYW1ldGVyIikgKwogICAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKHdpbmRvd19yYW5nZVsxLCBzdGFydF0sIHdpbmRvd19yYW5nZVsuTiwgZW5kXSkvNjAsIGNsaXAgPSAib2ZmIikgKwogICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpICsKICAgIHRoZW1lKHBsb3QubWFyZ2luID0gbWFyZ2luKDIxLCAxNCwgNywgNyksCiAgICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKQogIAogIAogICMgVHJhbnNmb3JtIHNjYWxlcyBpZiByZXF1aXJlZAogIGlmIChsb2dfeCkgewogICAgcCA8LSBwICsKICAgICAgc2NhbGVfeF9sb2cxMCgKICAgICAgICBicmVha3MgPSBzY2FsZXM6OnRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54KSwKICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnRyYW5zX2Zvcm1hdCgibG9nMTAiLCBzY2FsZXM6Om1hdGhfZm9ybWF0KDEwXi54KSksCiAgICAgICAgZXhwYW5kID0gYygwLCAwKQogICAgICApICsKICAgICAgYW5ub3RhdGlvbl9sb2d0aWNrcyhzaWRlcyA9ICJiIiwgb3V0c2lkZSA9IFQpCiAgfQogIAogIGlmIChsb2dfeSkgewogICAgcCA8LSBwICsKICAgICAgc2NhbGVfeV9sb2cxMCgpICsKICAgICAgYW5ub3RhdGlvbl9sb2d0aWNrcyhzaWRlcyA9ICJsIiwgb3V0c2lkZSA9IFQpCiAgfQoKICBpZiAocHJpbnRfcGxvdCkgcHJpbnQocCkKICByZXR1cm4gKHApCgp9CmBgYAoKCiMgU2luZ2xlIHBhcmFtZXRlcgoKV2hhdCBkb2VzIHByZWRpY3RlZCByZXRlbnRpb24gbG9vayBsaWtlIGlmIHdlIHVzZSBhIHNpbmdsZSBwYXJhbWV0ZXIgY29uZmlndXJhdGlvbiBmaXR0ZWQgYWNyb3NzIHRoZSB3aG9sZSByYW5nZSBvZiBpbnRlcnZhbHM/CgpEaXZpZGUgdGhlIGRhdGEgaW50byBsZWFybmluZyBzZXF1ZW5jZXMgZm9yIHBsb3R0aW5nOgpgYGB7cn0KZF9zaW5nbGUgPC0gY29weShkX2xhc3QpCmRfc2luZ2xlWywgc2VxdWVuY2UgOj0gMTouTl0KZF9zaW5nbGUgPC0gZF9mW2Rfc2luZ2xlWywgLihpZCwgc2VxdWVuY2UpXSwgb24gPSAuKGlkKV0KZF9zaW5nbGVbLCB3aW5kb3cgOj0gMV0KZF9zZXFfbGlzdF9zaW5nbGUgPC0gZ2VuZXJhdGVfc2VxX2xpc3QoZF9zaW5nbGUpCmBgYAoKCiMjIFdob2xlIGRhdGEgc2V0CgojIyMgVGhyZXNob2xkCgpgYGB7cn0Kc2luZ2xlX3RhdSA8LSBscl90YXVbbl93aW5kb3dzID09IDEsIHRhdV0KYGBgCgpUaGUgYmVzdC1maXR0aW5nIHRocmVzaG9sZCBmb3IgdGhlIGVudGlyZSByYW5nZSBvZiBpbnRlcnZhbHMgaXMgYHIgc2luZ2xlX3RhdWAuCklmIHdlIGNvbnNpc3RlbnRseSB1c2UgdGhpcyB0aHJlc2hvbGQgaW4gdGhlIG1vZGVsLCBpdCBpbml0aWFsbHkgb3ZlcnByZWRpY3RzIHJldGVudGlvbiAoaW5kaWNhdGluZyB0aGF0IHRoZSB0aHJlc2hvbGQgaXMgdG9vIGxvdyByZWxhdGl2ZSB0byB0aGUgYWN0aXZhdGlvbiksIGdldHMgdGhlIHByZWRpY3Rpb24gcmlnaHQgYXJvdW5kIHRoZSBtb2RlIG9mIHRoZSBpbnRlcnZhbCBkaXN0cmlidXRpb24sIGFuZCBldmVudHVhbGx5IHVuZGVycHJlZGljdHMgcmV0ZW50aW9uIChpbmRpY2F0aW5nIHRoYXQgdGhlIHRocmVzaG9sZCBpcyB0b28gaGlnaCkuCmBgYHtyfQpwcmVkX3NpbmdsZV90YXUgPC0gcHJlZGljdF9yZWNhbGwoc2VxX2xpc3QgPSBkX3NlcV9saXN0X3NpbmdsZSwgdGF1ID0gc2luZ2xlX3RhdSkKZF9zaW5nbGVfdGF1IDwtIGRfbGFzdFtwcmVkX3NpbmdsZV90YXUsIG9uID0gLihpZCldCgpwX3NpbmdsZV90YXUgPC0gcGxvdF9jb21wYXJpc29uKGRfbW9kZWwgPSBkX3NpbmdsZV90YXUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsX3BvcyA9IGxpc3QoZGF0YSA9IGxpc3QoeCA9IDM1MDAwLCB5ID0gLjUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gbGlzdCh4ID0gNzAwMCwgeSA9IC4yOSkpKQpgYGAKCgojIyMgRGVjYXkKCldlIGRpZCBub3QgZGV0ZXJtaW5lIHRoZSBkZWNheSBkaXJlY3RseSwgYnV0IGRlcml2ZWQgaXQgZnJvbSB0aGUgYmVzdC1maXR0aW5nIGFjdGl2YXRpb24uCkFjcm9zcyB0aGUgd2hvbGUgZGF0YSBzZXQsIHRoZSBiZXN0LWZpdHRpbmcgYWN0aXZhdGlvbiBpcyBgciBscl9hY3RpdmF0aW9uW25fd2luZG93cyA9PSAxLCBhY3RpdmF0aW9uXWAuCldlIGRldGVybWluZWQgdGhlIG9wdGltYWwgZGVjYXkgZm9yIGVhY2ggaXRlbSwgc3VjaCB0aGF0IGl0IGVuZGVkIHVwIGF0IHRoaXMgYWN0aXZhdGlvbi4KU2luY2UgYWN0aXZhdGlvbiAoYW5kIHRoZSB0aHJlc2hvbGQpIGFyZSBjb25zdGFudCBhY3Jvc3MgdGhlIHJhbmdlLCB0aGUgcHJlZGljdGVkIHJlY2FsbCBpcyBhbHNvIHNpbXBseSB0aGUgYmVzdC1maXR0aW5nIGhvcml6b250YWwgbGluZSB0aHJvdWdoIHRoZSBkYXRhLgpgYGB7cn0KcHJlZF9zaW5nbGVfZCA8LSBwcmVkaWN0X3JlY2FsbChkX3NlcV9saXN0X3NpbmdsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWNheSA9IGJzX2RfaW5kaXZbbl93aW5kb3dzID09IDFdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXUgPSBscl9hY3RpdmF0aW9uW25fd2luZG93cyA9PSAxLCB0YXVdKQpkX3NpbmdsZV9kIDwtIGRfbGFzdFtwcmVkX3NpbmdsZV9kLCBvbiA9IC4oaWQpXQoKcF9zaW5nbGVfZCA8LSBwbG90X2NvbXBhcmlzb24oZF9tb2RlbCA9IGRfc2luZ2xlX2QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3cgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuNzgpKSkKYGBgCgoKCiMjIyBTY2FsaW5nIGZhY3RvcgoKTGlrZSB0aGUgZGVjYXksIHRoZSBvcHRpbWFsIHNjYWxpbmcgZmFjdG9yIHdhcyBkZXJpdmVkIGZyb20gdGhlIGJlc3QtZml0dGluZyBhY3RpdmF0aW9uLgpJbiBwcmluY2lwbGUgdGhpcyBzaG91bGQgYWdhaW4gcHJvZHVjZSBhIGhvcml6b250YWwgbGluZS4KSG93ZXZlciwgZ2V0dGluZyB0aGlzIGFjdGl2YXRpb24gdmFsdWUgd291bGQgcmVxdWlyZSBzY2FsaW5nIGJ5IGEgZmFjdG9yIGxhcmdlciB0aGFuIDEgZm9yIGEgbGFyZ2UgcG9ydGlvbiBvZiB0aGUgZGF0YS4KU2luY2UgaCBpcyBsaW1pdGVkIHRvIDEsIHdlIGNhbm5vdCBnZXQgdGhlIGFjdGl2YXRpb24gbG93IGVub3VnaCBpbiB0aGVzZSBjYXNlcywgYW5kIHNvIHdlIG92ZXJwcmVkaWN0IHJldGVudGlvbi4KYGBge3J9CnByZWRfc2luZ2xlX2ggPC0gcHJlZGljdF9yZWNhbGwoZF9zZXFfbGlzdF9zaW5nbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaCA9IGJzX2hfaW5kaXZbbl93aW5kb3dzID09IDFdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhdSA9IGxyX2FjdGl2YXRpb25bbl93aW5kb3dzID09IDEsIHRhdV0pCmRfc2luZ2xlX2ggPC0gZF9sYXN0W3ByZWRfc2luZ2xlX2gsIG9uID0gLihpZCldCgpwX3NpbmdsZV9oIDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gZF9zaW5nbGVfaCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wb3MgPSBsaXN0KGRhdGEgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC41KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC43OCkpKQpgYGAKCgojIyAyNC1ob3VyIGludGVydmFsCgpSYXRoZXIgdGhhbiBkZXRlcm1pbmluZyB0aGUgYmVzdCBwYXJhbWV0ZXIgZml0IGZyb20gdGhlIHdob2xlIGRhdGEgc2V0LCB3ZSBhbHNvIGV4cGxvcmUgYSBzY2VuYXJpbyBpbiB3aGljaCB3ZSBvbmx5IGhhdmUgaW50ZXJ2YWxzIGFyb3VuZCAyNCBob3Vycywgd2hpY2ggaXMgbW9yZSBzaW1pbGFyIHRvIG1hbnkgY29udHJvbGxlZCBleHBlcmltZW50cy4KCldlIHVzZSBhIHN1YnNldCBvZiB0aGUgZGF0YSB3aXRoIGludGVydmFscyBhcm91bmQgMjQgaG91cnMuCkluIHRoZSAyMC13aW5kb3cgZml0IHRoaXMgZmFsbHMgcXVpdGUgbmVhdGx5IHdpdGhpbiB3aW5kb3cgbnVtYmVyIDEyLCBzbyB3ZSdsbCB1c2UgdGhhdC4KYGBge3J9CmdncGxvdCh3aW5kb3dfcmFuZ2Vbbl93aW5kb3dzID09IDIwXSkgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSAyNCo2MCo2MCksIGNvbG91ciA9ICJyZWQiLCBsdHkgPSAyKSArCiAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdCA9IDEyKSwgY29sb3VyID0gInJlZCIsIGx0eSA9IDIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSBzdGFydCwgeGVuZCA9IGVuZCwgeSA9IHdpbmRvdywgeWVuZCA9IHdpbmRvdykpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gZ2VvbV9tZWFuLCB5ID0gd2luZG93KSkgKwogIGdlb21fdGV4dChhZXMoeCA9IGdlb21fbWVhbiwgeSA9IHdpbmRvdyArIC41LCBsYWJlbCA9IHdpbmRvdyksIGNvbG91ciA9ICJibHVlIikgKwogIHNjYWxlX3hfbG9nMTAoCiAgICBicmVha3MgPSBzY2FsZXM6OnRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54KSwKICAgIGxhYmVscyA9IHNjYWxlczo6dHJhbnNfZm9ybWF0KCJsb2cxMCIsIHNjYWxlczo6bWF0aF9mb3JtYXQoMTBeLngpKSwKICAgIGV4cGFuZCA9IGMoMCwgMCkKICApICsKICBsYWJzKHggPSAiQmV0d2Vlbi1zZXNzaW9uIGludGVydmFsIChtaW51dGVzKSIsCiAgICAgICB5ID0gIldpbmRvdyIpICsKICBhbm5vdGF0aW9uX2xvZ3RpY2tzKHNpZGVzID0gImIiLCBvdXRzaWRlID0gVCkgKwogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gIm9mZiIpCmBgYAoKIyMjIFRocmVzaG9sZAoKYGBge3J9CnRhdV8yNGggPC0gbHJfdGF1W25fd2luZG93cyA9PSAyMCAmIHdpbmRvdyA9PSAxMiwgdGF1XQpgYGAKClRoZSBvcHRpbWFsIHRocmVzaG9sZCBmb3IgdGhlIDI0aCBpbnRlcnZhbCBpcyBgciB0YXVfMjRoYCwgcXVpdGUgc2ltaWxhciB0byB0aGUgb3B0aW1hbCB0aHJlc2hvbGQgZm9yIHRoZSB3aG9sZSBkYXRhIHNldC4KVGhlIG1vZGVsJ3MgcHJlZGljdGlvbnMgYWxzbyBsb29rIHZlcnkgc2ltaWxhci4KYGBge3J9CnByZWRfdGF1XzI0aCA8LSBwcmVkaWN0X3JlY2FsbChzZXFfbGlzdCA9IGRfc2VxX2xpc3Rfc2luZ2xlLCB0YXUgPSB0YXVfMjRoKQpkX3RhdV8yNGggPC0gZF9sYXN0W3ByZWRfdGF1XzI0aCwgb24gPSAuKGlkKV0KCnBfdGF1XzI0aCA8LSBwbG90X2NvbXBhcmlzb24oZF9tb2RlbCA9IGRfdGF1XzI0aCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSA1MDAwLCB5ID0gLjI5KSkpICsKICBnZW9tX3JlY3QoZGF0YSA9IHdpbmRvd19yYW5nZVtuX3dpbmRvd3MgPT0gMjAgJiB3aW5kb3cgPT0gMTJdLCBhZXMoeG1pbiAgPSBzdGFydC82MCwgeG1heCA9IGVuZC82MCwgeW1pbiA9IC0wLjA1LCB5bWF4ID0gMS4wNSksIGZpbGwgPSBzZWN0aW9uX2NvbCwgYWxwaGEgPSAuMjUpCgpwX3RhdV8yNGgKYGBgCgojIyMgRGVjYXkKCkJlY2F1c2Ugb3B0aW1hbCBkZWNheSBpcyBkZXJpdmVkIHNlcGFyYXRlbHkgZm9yIGVhY2ggaW5kaXZpZHVhbCBsZWFybmluZyBzZXF1ZW5jZSwgdGhlcmUgaXMgbm90IGEgc2luZ2xlIGRlY2F5IHZhbHVlIGFzc29jaWF0ZWQgd2l0aCB0aGUgMjRoIGludGVydmFsLCBhbmQgd2Ugb25seSBrbm93IHRoZSBiZXN0IGRlY2F5IGZvciB0aGUgaW5kaXZpZHVhbCBzZXF1ZW5jZXMgdGhhdCBmYWxsIHdpdGhpbiB0aGUgMjRoIHNldC4KCmBgYHtyfQpkXzI0aCA8LSBtZWRpYW4oYnNfZF9pbmRpdltuX3dpbmRvd3MgPT0gMjAgJiB3aW5kb3cgPT0gMTIsIGRdKQpgYGAKClRvIGdldCBhIHBhcmFtZXRlciB0aGF0IHdlIGNhbiB1c2UgZm9yIGdlbmVyYWwgcHJlZGljdGlvbiwgd2UnbGwgdGFrZSB0aGUgbWVkaWFuOiBkID0gYHIgZF8yNGhgLgoKYGBge3J9CmdncGxvdChic19kX2luZGl2W25fd2luZG93cyA9PSAyMCAmIHdpbmRvdyA9PSAxMl0sIGFlcyh4ID0gZCkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IC4wMDUsIGZpbGwgPSAibWlkbmlnaHRibHVlIikgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBkXzI0aCksIGNvbG91ciA9ICJyZWQiLCBsdHkgPSAyKQpgYGAKClRoZXNlIHByZWRpY3Rpb25zIHNob3cgdGhlIHR5cGljYWwgcGF0dGVybjogdGhlIG1vZGVsIGluaXRpYWxseSBvdmVycHJlZGljdHMgcmVjYWxsLCBnZXRzIGl0IHJpZ2h0IGFyb3VuZCAyNGgsIGFuZCBldmVudHVhbGx5IHVuZGVycHJlZGljdHMgcmVjYWxsLiAKYGBge3J9CnByZWRfZF8yNGggPC0gcHJlZGljdF9yZWNhbGwoZF9zZXFfbGlzdF9zaW5nbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVjYXkgPSBkXzI0aCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGF1ID0gbHJfYWN0aXZhdGlvbltuX3dpbmRvd3MgPT0gMjAgJiB3aW5kb3cgPT0gMTIsIHRhdV0pCmRfZF8yNGggPC0gZF9sYXN0W3ByZWRfZF8yNGgsIG9uID0gLihpZCldCgpwX2RfMjRoIDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gZF9kXzI0aCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wb3MgPSBsaXN0KGRhdGEgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC41KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSA1MDAwLCB5ID0gLjc4KSkpICsKICBnZW9tX3JlY3QoZGF0YSA9IHdpbmRvd19yYW5nZVtuX3dpbmRvd3MgPT0gMjAgJiB3aW5kb3cgPT0gMTJdLCBhZXMoeG1pbiAgPSBzdGFydC82MCwgeG1heCA9IGVuZC82MCwgeW1pbiA9IC0wLjA1LCB5bWF4ID0gMS4wNSksIGZpbGwgPSBzZWN0aW9uX2NvbCwgYWxwaGEgPSAuMjUpCgpwX2RfMjRoCmBgYAoKCiMjIyBTY2FsaW5nIGZhY3RvcgoKYGBge3J9CmhfMjRoIDwtIG1lZGlhbihic19oX2luZGl2W25fd2luZG93cyA9PSAyMCAmIHdpbmRvdyA9PSAxMiwgaF0pCmBgYAoKQXMgd2l0aCBkZWNheSwgd2UnbGwgdGFrZSB0aGUgbWVkaWFuIHNjYWxpbmcgZmFjdG9yIGZyb20gdGhlIDI0aCBzZXQ6IGggPSBgciBoXzI0aGAuCgpgYGB7cn0KZ2dwbG90KGJzX2hfaW5kaXZbbl93aW5kb3dzID09IDIwICYgd2luZG93ID09IDEyXSwgYWVzKHggPSBoKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gLjAxLCBmaWxsID0gIm1pZG5pZ2h0Ymx1ZSIpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gaF8yNGgpLCBjb2xvdXIgPSAicmVkIiwgbHR5ID0gMikKYGBgCgpUaGlzIG1vZGVsIGFscmVhZHkgZG9lcyBzdXJwcmlzaW5nbHkgd2VsbDogc2hvcnQtdGVybSBwcmVkaWN0aW9ucyBhcmUgZ29vZCwgdGhvdWdoIHRoZSBtb2RlbCBpcyB0b28gb3B0aW1pc3RpYyBvbiBpbnRlcnZhbHMgb2YgMS0yNCBob3VycywgYW5kIHRvbyBwZXNzaW1pc3RpYyBvbiBpbnRlcnZhbHMgbG9uZ2VyIHRoYW4gYSB3ZWVrLgpgYGB7cn0KcHJlZF9oXzI0aCA8LSBwcmVkaWN0X3JlY2FsbChkX3NlcV9saXN0X3NpbmdsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoID0gaF8yNGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGF1ID0gbHJfYWN0aXZhdGlvbltuX3dpbmRvd3MgPT0gMjAgJiB3aW5kb3cgPT0gMTIsIHRhdV0pCmRfaF8yNGggPC0gZF9sYXN0W3ByZWRfaF8yNGgsIG9uID0gLihpZCldCgpwX2hfMjRoIDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gZF9oXzI0aCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wb3MgPSBsaXN0KGRhdGEgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC41KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSAxMjAwMCwgeSA9IC4yKSkpICsKICBnZW9tX3JlY3QoZGF0YSA9IHdpbmRvd19yYW5nZVtuX3dpbmRvd3MgPT0gMjAgJiB3aW5kb3cgPT0gMTJdLCBhZXMoeG1pbiAgPSBzdGFydC82MCwgeG1heCA9IGVuZC82MCwgeW1pbiA9IC0wLjA1LCB5bWF4ID0gMS4wNSksIGZpbGwgPSBzZWN0aW9uX2NvbCwgYWxwaGEgPSAuMjUpCgpwX2hfMjRoCmBgYAoKIyMgU2hvcnQgaW50ZXJ2YWxzCgojIyMgVGhyZXNob2xkCgpGaW5hbGx5LCB3ZSdsbCBsb29rIGF0IGEgbW9kZWwgd2l0aCBkZWZhdWx0IHBhcmFtZXRlcnMgb3RoZXIgdGhhbiB0aGUgdGhyZXNob2xkLCB3aGljaCBpcyBmaXR0ZWQgdG8gc2hvcnQgaW50ZXJ2YWxzICgwLTEwIG1pbnV0ZXMpIG9ubHkuCgpgYGB7cn0KcHJlZF90YXVfc2hvcnQgPC0gcHJlZGljdF9yZWNhbGwoZF9zZXFfbGlzdF9zaW5nbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhdSA9IHRhdV9zaG9ydCR0YXVfc2hvcnQpCmRfdGF1X3Nob3J0IDwtIGRfbGFzdFtwcmVkX3RhdV9zaG9ydCwgb24gPSAuKGlkKV0KCnBfdGF1X3Nob3J0IDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gZF90YXVfc2hvcnQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wb3MgPSBsaXN0KGRhdGEgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC41KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuMDgpKSkgKwogIGdlb21fcmVjdChhZXMoeG1pbiAgPSBtaW4od2luZG93X3JhbmdlJHN0YXJ0KS82MCwgeG1heCA9IDEwLCB5bWluID0gLTAuMDUsIHltYXggPSAxLjA1KSwgZmlsbCA9IHNlY3Rpb25fY29sLCBhbHBoYSA9IC4yNSkKCnBfdGF1X3Nob3J0CmBgYAoKCiMgVGltZS1kZXBlbmRlbnQgcGFyYW1ldGVycwoKUmF0aGVyIHRoYW4gdXNpbmcgYSBzaW5nbGUgcGFyYW1ldGVyIGNvbmZpZ3VyYXRpb24gYWNyb3NzIHRoZSByYW5nZSwgd2UgY2FuIGFsc28gZml0IHBhcmFtZXRlcnMgc2VwYXJhdGVseSBwZXIgdGltZSBiaW4uCkhlcmUgd2UnbGwgbG9vayBhdCB0aGUgcmVzdWx0cyB3aGVuIHRoZSBkYXRhIGFyZSBzcGxpdCBpbnRvIDIwIGJpbnMsIGVhY2ggY29udGFpbmluZyA1JSBvZiB0aGUgdG90YWwuCgpQcmVwYXJlIHRoZSBkYXRhOgpgYGB7cn0KZF8yMCA8LSBjb3B5KGRfbGFzdCkKZF8yMCA8LSBkXzIwWywgc2VxdWVuY2UgOj0gMTouTl1bLCAuKGlkLCBzZXF1ZW5jZSwgdGltZV9iZXR3ZWVuLCB3aW5kb3cgPSBudGlsZSh0aW1lX2JldHdlZW4sIDIwKSldCmRfMjBfc2VxIDwtIGdlbmVyYXRlX3NlcV9saXN0KGRfMjBbZF9mLCBvbiA9IC4oaWQpXSkKYGBgCgoKIyMgVGhyZXNob2xkCgpgYGB7cn0KcHJlZF90YXVfMjAgPC0gcHJlZGljdF9yZWNhbGwoc2VxX2xpc3QgPSBkXzIwX3NlcSwgdGF1ID0gbHJfdGF1LCB3aW5kb3dzID0gMjApCmRfdGF1XzIwIDwtIGRfbGFzdFtwcmVkX3RhdV8yMCwgb24gPSAuKGlkKV0KCnBfdGF1XzIwIDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gZF90YXVfMjAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gMjAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IGxpc3QoeCA9IDIwMDAwLCB5ID0gLjI5KSkpCmBgYAoKUGFyYW1ldGVyIGNoYW5nZSBvdmVyIHRpbWU6CmBgYHtyfQpscl90YXVfdml6IDwtIGxyX3RhdVt3aW5kb3dfcmFuZ2UsIG9uID0gLihuX3dpbmRvd3MsIHdpbmRvdyldCnNldG5hbWVzKGxyX3RhdV92aXosICJ0YXUiLCAicGFyYW1ldGVyIikKCnBfdGF1X3RpbWUgPC0gcGxvdF9wYXJhbWV0ZXIoZF9wYXJhbWV0ZXIgPSBscl90YXVfdml6LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IDIwKQpgYGAKCgojIyBEZWNheQoKYGBge3J9CnByZWRfZF8yMCA8LSBwcmVkaWN0X3JlY2FsbChzZXFfbGlzdCA9IGRfMjBfc2VxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVjYXkgPSBic19kX2luZGl2W25fd2luZG93cyA9PSAyMF0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGF1ID0gbHJfYWN0aXZhdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpbmRvd3MgPSAyMCkKCmRfZF8yMCA8LSBkX2xhc3RbcHJlZF9kXzIwLCBvbiA9IC4oaWQpXQoKcF9kXzIwIDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gZF9kXzIwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBuX3cgPSAyMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzAwMDAsIHkgPSAuMyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSA0MDAwMCwgeSA9IC41NCkpKQpgYGAKClBhcmFtZXRlciBjaGFuZ2Ugb3ZlciB0aW1lOgpgYGB7cn0KYnNfZF92aXogPC0gYnNfZF9pbmRpdlt3aW5kb3dfcmFuZ2UsIG9uID0gLihuX3dpbmRvd3MsIHdpbmRvdyldWywgLihkID0gbWVkaWFuKGQpKSwgYnkgPSAuKG5fd2luZG93cywgd2luZG93LCBzdGFydCwgZW5kLCBnZW9tX21lYW4pXQpzZXRuYW1lcyhic19kX3ZpeiwgImQiLCAicGFyYW1ldGVyIikKCnBfZF90aW1lIDwtIHBsb3RfcGFyYW1ldGVyKGRfcGFyYW1ldGVyID0gYnNfZF92aXosCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IDIwKQpgYGAKIyMgU2NhbGluZyBmYWN0b3IKCmBgYHtyfQpwcmVkX2hfMjAgPC0gcHJlZGljdF9yZWNhbGwoc2VxX2xpc3QgPSBkXzIwX3NlcSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGggPSBic19oX2luZGl2W25fd2luZG93cyA9PSAyMF0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGF1ID0gbHJfYWN0aXZhdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpbmRvd3MgPSAyMCkKCmRfaF8yMCA8LSBkX2xhc3RbcHJlZF9oXzIwLCBvbiA9IC4oaWQpXQoKcF9oXzIwIDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gZF9oXzIwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBuX3cgPSAyMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzAwMDAsIHkgPSAuMyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSA0MDAwMCwgeSA9IC41NCkpKQpgYGAKClBhcmFtZXRlciBjaGFuZ2Ugb3ZlciB0aW1lOgpgYGB7cn0KYnNfaF92aXogPC0gYnNfaF9pbmRpdlt3aW5kb3dfcmFuZ2UsIG9uID0gLihuX3dpbmRvd3MsIHdpbmRvdyldWywgLihoID0gbWVkaWFuKGgpKSwgYnkgPSAuKG5fd2luZG93cywgd2luZG93LCBzdGFydCwgZW5kLCBnZW9tX21lYW4pXQpzZXRuYW1lcyhic19oX3ZpeiwgImgiLCAicGFyYW1ldGVyIikKCnBfaF90aW1lIDwtIHBsb3RfcGFyYW1ldGVyKGRfcGFyYW1ldGVyID0gYnNfaF92aXosCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvZ195ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gMjApCmBgYAoKIyBJbnRlcnZhbCBkaXN0cmlidXRpb24KCkEgaGlzdG9ncmFtIG9mIHRoZSBiZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWxzIGluIHRoZSBkYXRhLgpgYGB7cn0KcF9oaXN0b2dyYW0gPC0gZ2dwbG90KCkgKwogICMgV2luZG93IGJhY2tncm91bmQKICBnZW9tX3JlY3QoZGF0YSA9IHdpbmRvd19yYW5nZVtuX3dpbmRvd3MgPT0gMV0sIGFlcyh4bWluID0gc3RhcnQvNjAsIHhtYXggPSBlbmQvNjAsIHltaW4gPSAtSW5mLCB5bWF4ID0gSW5mKSwgZmlsbCA9IHdpbmRvd19jb2wsIGFscGhhID0gLjEpICsKICAjIFRpbWUgbWFya2VycyBhbG9uZyB0aGUgdG9wCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gbGFiZWxfeCwgeGVuZCA9IGxhYmVsX3gsIHkgPSAxLjA1LCB5ZW5kID0gMS4wNjUpLCBjb2xvdXIgPSAiZ3JleTIwIikgKwogIGdlb21fdGV4dChhZXMoeCA9IGxhYmVsX3gsIHkgPSBsYWJlbF95LCBsYWJlbCA9IGxhYmVsX3R4dCksIGNvbG91ciA9ICJncmV5MzAiKSArCiAgIyBIaXN0b2dyYW0KICBnZW9tX2hpc3RvZ3JhbShkYXRhID0gZF9sYXN0LCBhZXMoeCA9IHRpbWVfYmV0d2Vlbi82MCwgeSA9IC4ubmNvdW50Li4pLCBiaW5zID0gMTAwLCBmaWxsID0gb2JzX2NvbCkgKwogICMgUGxvdCBzZXR1cAogIHNjYWxlX3hfbG9nMTAoCiAgICBicmVha3MgPSBzY2FsZXM6OnRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54KSwKICAgIGxhYmVscyA9IHNjYWxlczo6dHJhbnNfZm9ybWF0KCJsb2cxMCIsIHNjYWxlczo6bWF0aF9mb3JtYXQoMTBeLngpKSwKICAgIGV4cGFuZCA9IGMoMCwgMCkKICApICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEsIGJ5ID0gLjI1KSkgKwogIGxhYnMoeCA9ICJCZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWwgKG1pbnV0ZXMpIiwKICAgICAgIHkgPSAiRGVuc2l0eSIpICsKICBhbm5vdGF0aW9uX2xvZ3RpY2tzKHNpZGVzID0gImIiLCBvdXRzaWRlID0gVCkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCAxKSwgeGxpbSA9IGMod2luZG93X3JhbmdlWzEsIHN0YXJ0XSwgd2luZG93X3JhbmdlWy5OLCBlbmRdKS82MCwgY2xpcCA9ICJvZmYiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpICsKICB0aGVtZShwbG90Lm1hcmdpbiA9IG1hcmdpbigyMSwgMTQsIDcsIDcpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkKCnBfaGlzdG9ncmFtCmBgYAoKCiMgQ29tYmluZWQgZmlndXJlcwoKIyMgTW9kZWwgZml0cwpgYGB7cn0KcGxvdF9ncmlkKAogIHBfaGlzdG9ncmFtLAogIHBfdGF1X3Nob3J0LAogIHBfdGF1XzI0aCwKICBwX3RhdV8yMCwKICBwX2RfMjRoLAogIHBfZF8yMCwKICBwX2hfMjRoLAogIHBfaF8yMCwKICBuY29sID0gMiwKICBsYWJlbHMgPSBjKCJBXHRcdEludGVydmFsIGRpc3RyaWJ1dGlvbiIsICJCXHRcdFRocmVzaG9sZCBvcHRpbWlzZWQgZm9yIDAgLSAxMCBtaW4iLCAiQ1x0XHRUaHJlc2hvbGQgb3B0aW1pc2VkIGZvciAyNGgiLCAiRFx0XHRJbnRlcnZhbC1kZXBlbmRlbnQgdGhyZXNob2xkIiwgIkVcdFx0RGVjYXkgb3B0aW1pc2VkIGZvciAyNGgiLCAiRlx0XHRJbnRlcnZhbC1kZXBlbmRlbnQgZGVjYXkiLCAiR1x0XHRTY2FsaW5nIGZhY3RvciBvcHRpbWlzZWQgZm9yIDI0aCIsICJIXHRcdEludGVydmFsLWRlcGVuZGVudCBzY2FsaW5nIGZhY3RvciIpLAogIGFsaWduID0gImh2IiwKICBsYWJlbF94ID0gLjAyNSwKICBoanVzdCA9IDAsCiAgc2NhbGUgPSAuOQopICsKICB0aGVtZShwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIsIGNvbG91ciA9IE5BKSkKCmdnc2F2ZShmaWxlLnBhdGgoIi4uIiwgIm91dHB1dCIsICJtb2RlbF9maXR0aW5nX3Jlc3VsdHMucG5nIiksIHdpZHRoID0gMTAsIGhlaWdodCA9IDE1KQpgYGAKCiMjIFBhcmFtZXRlcnMgb3ZlciB0aW1lCgpgYGB7cn0KcGxvdF9ncmlkKAogIHBfdGF1X3RpbWUsIHBfZF90aW1lLCBwX2hfdGltZSwKICBuY29sID0gMywgCiAgbGFiZWxzID0gYygiQVx0XHRJbnRlcnZhbC1kZXBlbmRlbnQgdGhyZXNob2xkIiwgIkJcdFx0SW50ZXJ2YWwtZGVwZW5kZW50IGRlY2F5IiwgIkNcdFx0SW50ZXJ2YWwtZGVwZW5kZW50IGgiKSwKICBhbGlnbiA9ICJodiIsCiAgbGFiZWxfeCA9IC4wMjUsCiAgaGp1c3QgPSAwCikgKwogIHRoZW1lKHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiwgY29sb3VyID0gTkEpKQoKZ2dzYXZlKGZpbGUucGF0aCgiLi4iLCAib3V0cHV0IiwgInBhcmFtc190aW1lLnBuZyIpLCB3aWR0aCA9IDEyLCBoZWlnaHQgPSA0KQpgYGAKCg==